// Copyright 2022 a.o.smith ALL Rights Reserved
package com.liuxinlong.auth;

import com.liuxinlong.common.AosException;
import com.liuxinlong.constant.ThreadLocalConstants;
import com.liuxinlong.enums.ReturnCodes;
import com.liuxinlong.modules.dao.UserRoleRelationDao;
import com.liuxinlong.modules.entity.User;
import com.liuxinlong.modules.entity.UserRoleRelation;
import com.liuxinlong.modules.mapper.UserMapper;
import com.liuxinlong.modules.system.service.RoleRightService;
import com.liuxinlong.utils.JwtTokenUtils;
import com.liuxinlong.utils.RedisUtils;
import com.liuxinlong.utils.ThreadlocalUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 登陆拦截器
 *
 * @author liuxinlong@hotwater.com.cn
 * @version 1.0.0
 * @since 2022年1月20日
 */
@Component
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {

    private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList("/user/current", "/common/notices", "/common/test", "/dashboard/graph", "/dashboard/total",
                    "/dashboard/detail", "/dashboard/rotation", "/user/logout", "/tree/menu", "/tree/hierarchy",
                    "/tree/expert-group", "/common-select/department", "/common-select/user", "/common-select/role",
                    "/common-select/post","/common-select/user-level","/common-select/team",
                    "/common-select/question-deal", "/common-select/question-status", "/common-select/classification",
                    "/common-select/standard", "/common-select/user-group", "/common-select/material", "/file/query",
                    "/file/import", "/file/download", "/file/preview", "/question/test/record", "/common/generate-code",
                    "/common-select/plate", "/user/info/query", "/user/info/update", "/user/base", "/user/password",
                    "/work/query", "/work/need", "/work/done", "/work/trends", "/bar/browse/luck-prize/list",
                    "/bar/browse/luck-prize/start", "/common/user-header", "/common/read-change", "/common/read-clear",
                    "/common-select/file-business", "/common-select/expert-sub", "/common-select/classification-sub", "/user/standard",
                    "/user/standard-update", "/user/standard-delete", "/common-select/train", "/sync/parent",
                    "/common-select/message-source", "/faq/count", "/butler/train/count", "/common-select/patent-sub",
                    "/file/get", "/common-select/points-business", "/common-select/points-source",
                    "/common-select/train-business", "/common-select/train-sub", "/common-select/message-content",
                    "/common-select/expert-group", "/common-select/expert", "/common-select/luck", "/common-select/present",
                    "/common-select/redis", "/common/online-count", "/convolution/material-get", "/file/three",
                    "/common-select/convolution-status", "/common/follow", "/work/follow", "/work/fans",
                    "/work/workflow-apply", "/work/workflow-cc", "/work/workflow-all", "/work/workflow-detail", "/work/workflow-deal",
                    "/common-select/patent-department", "/common-select/patent-product", "/common-select/assets-project-type",
                    "/common-select/assets-type", "/common-select/assets-budget", "/common-select/assets-department",
                    "/common-select/ecn-change-type", "/common-select/project", "/common-select/patent-risk",
                    "/common-select/workflow-type", "/common-select/workflow-group", "/common-select/workflow-config",
                    "/common-select/project-stage", "/common-select/investment-expenses-type",
                    "/common-select/investment-loss-rate-type",  "/common-select/investment-turnover-type",
                    "/common/matter-deal","/common/project-detail")));

    private static final Set<String> ALLOWED_ADMIN_PATHS = Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList("/right/query", "/right/update", "/right/query", "/right/query", "/right/query", "/right/query",
                    "/right/query", "/moc/ecn/file-sync", "/quartz/page", "/quartz/enable", "/quartz/add", "/quartz/update",
                    "/quartz/start", "/quartz/pause", "/quartz/suspend", "/quartz/resume", "/quartz/reschedule", "/user/reset",
                    "/user/offline", "/user/black", "/backup/page", "/backup/start", "/backup/export",
                    "/mike/import", "/mike/generate", "/workflow/generate-data")));

    @Autowired
    private JwtTokenUtils jwtTokenUtils;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private Audience audience;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserRoleRelationDao userRoleRelationDao;

    @Autowired
    private RoleRightService roleRightService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 判断接口是否需要登录
        NoAuth noAuth = method.getAnnotation(NoAuth.class);

        //!=null表示该方法存在此注解，是我们需要放行的方法。
        if (noAuth != null) {
            System.out.println("No auth");
            return true;
        }

        String token = request.getHeader("token");

        // 判断接口是否为外部Ecn登录
        OutEcnAuth outEcnAuth = method.getAnnotation(OutEcnAuth.class);
        if (outEcnAuth != null) {
            return checkOutToken(token);
        }
        return checkToken(token) && checkPermission(request);
    }

    /**
     * token认证
     *
     * @param token 用户token
     * @return 是否认证成功
     */
    private boolean checkToken(String token) {
        // 执行认证
        if (null == token) {
            throw new AosException(ReturnCodes.HTTP_PARAM_ERROR, "token不存在，用户未登录");
        }
//		if (!token.startsWith("Bearer ")) {
//			throw new AosException(ReturnCodes.HTTP_PARAM_ERROR, "token格式有误，请重新登录");
//		}
        String userName = jwtTokenUtils.getUserName(token, audience.getBase64Secret());
        if (StringUtils.isEmpty(userName)) {
            return false;
        }
        ThreadlocalUtils.set(ThreadLocalConstants.USER_ACCOUNT, userName);
        User user = userMapper.getUserByAccount(userName);
        ThreadlocalUtils.set(ThreadLocalConstants.USER_INFO, user);
        // 获取redis中的token信息
        String tokenInRedis = redisUtils.getUserCache(user.getId());
        if (StringUtils.isEmpty(tokenInRedis)) {
            redisUtils.setUserStatus(user.getId(), 0);
            userMapper.updateUserStatus(user.getId(), 0, null);
            throw new AosException(ReturnCodes.HTTP_PERMISSION_TOKEN_EXPIRED);
//			return false;
        }
//		String realName = JwtUtil.getRealName(token);
//		long expireIn = JwtUtil.getExpireIn(token);
//
//		if (StringUtils.isEmpty(realName)) {
//			return false;
//		}
//		String userNameInRedis = JwtUtil.getUserName(tokenInRedis);
//		String realNameInRedis = JwtUtil.getRealName(tokenInRedis);
//		long expireInRedis = JwtUtil.getExpireIn(tokenInRedis);
//
//		if (StringUtils.isEmpty(userNameInRedis) || StringUtils.isEmpty(realNameInRedis)) {
//			return false;
//		}
//		// 判断token是否过期
//		if (expireIn != expireInRedis) {
//			return false;
//		}
//		// 判断token过期时间
//		return expireIn >= System.currentTimeMillis();
//		return true;
        // 验证token是否有效--无效已做异常抛出，由全局异常处理后返回对应信息
        JwtTokenUtils.parseJWT(token, audience.getBase64Secret());
        redisUtils.setUserCache(user.getId(), token);

        return true;

    }

    /**
     * token认证（外部使用）
     *
     * @param token 用户token
     * @return 是否认证成功
     */
    private boolean checkOutToken(String token) {
        // 执行认证
        if (null == token) {
            throw new AosException(ReturnCodes.HTTP_PARAM_ERROR, "token不存在，用户未登录");
        }
        String userName = jwtTokenUtils.getUserName(token, audience.getBase64Secret());
        if (StringUtils.isEmpty(userName)) {
            return false;
        }
        // 获取redis中的token信息
        String tokenInRedis = redisUtils.getUserCache(userName);
        if (StringUtils.isEmpty(tokenInRedis)) {
            throw new AosException(ReturnCodes.HTTP_PERMISSION_TOKEN_EXPIRED);
        }
        ThreadlocalUtils.set(ThreadLocalConstants.USER_ACCOUNT, userName);
        // 验证token是否有效--无效已做异常抛出，由全局异常处理后返回对应信息
        JwtTokenUtils.parseJWT(token, audience.getBase64Secret());
        redisUtils.setUserCache(userName, token);
        return true;
    }

    /**
     * 判断用户权限
     *
     * @param request 请求信息
     * @return 用户权限有效性
     */
    private boolean checkPermission(HttpServletRequest request) {
        String originalUrl = request.getRequestURI().substring(5);
        if (ALLOWED_PATHS.contains(originalUrl)) {
            return true;
        }
        User user = (User) ThreadlocalUtils.getCache(ThreadLocalConstants.USER_INFO);
        List<UserRoleRelation> roleRelationList = userRoleRelationDao.queryRelationByUserId(user.getId());
        if (roleRelationList.stream().anyMatch(item -> StringUtils.equals(item.getRoleId(), "1")) && ALLOWED_ADMIN_PATHS.contains(originalUrl)) {
            return true;
        }
        String url = originalUrl.replace("/query", "/page").replace("/list", "/page")
                .replace("/batch-add", "/import").replace("/batch-update", "/import").replace("/unlock", "/update").replace("/lock", "/update");
        if (!roleRightService.checkPermission(roleRelationList, url)) {
            throw new AosException(ReturnCodes.HTTP_PERMISSION_VERIFICATION_FAILED);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {

    }

}
